/**
 * TabVD is a Virtual Desktop Client/Server designed specifically for connecting
 * from Tablet like devices.
 *
 * Copyright (C) 2011 Subhabrata Ghosh
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * Global.java
 *
 *  Created on: Jul 9, 2011
 *      Author: subhagho
 */
package net.langene.tabvd.core;

import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;

import net.langene.tabvd.core.db.DbInstaller;
import net.langene.tabvd.core.db.Schema;

import org.apache.commons.configuration.XMLConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.util.StatusPrinter;

/**
 * @author subhagho
 * 
 */
public class Global {
	/**
	 * _config -> XMLConfiguration
	 */
	private static XMLConfiguration _config = null;

	/**
	 * _dbconn -> Connection
	 */
	private static Connection _dbconn = null;

	private static Schema _schema = null;

	/**
	 * Get a handle to the Global DB Schema object.
	 * 
	 * net.langene.tabvd.core.Global
	 * 
	 * @return
	 * @throws Exception
	 */
	public static Schema schema() throws Exception {
		if (!isInitialized)
			throw new Exception(
					"Global not initialized or initialization failed.");
		return _schema;
	}

	/**
	 * Get a handle to the Global Configuration object.
	 * 
	 * net.langene.tabvd.core.Global
	 * 
	 * @return
	 * @throws Exception
	 */
	public static XMLConfiguration config() throws Exception {
		if (!isInitialized)
			throw new Exception(
					"Global not initialized or initialization failed.");
		return _config;
	}

	/**
	 * Get a handle to the Database Connection.
	 * 
	 * net.langene.tabvd.core.Global
	 * 
	 * @return
	 * @throws Exception
	 */
	public static Connection connection() throws Exception {
		if (!isInitialized)
			throw new Exception(
					"Global not initialized or initialization failed.");
		return _dbconn;
	}

	/* Instance Factory methods */
	private static Boolean isInitialized = false;

	final static Logger logger = LoggerFactory.getLogger(Global.class);

	public static void dispose() {
		try {
			synchronized (isInitialized) {
				if (!isInitialized)
					return;
				if (_dbconn != null) {
					if (!_dbconn.isClosed())
						_dbconn.close();
					_dbconn = null;
				}
				logger.info("Server shutdown complete...");
			}
		} catch (Exception e) {
			logger.error("Error disposing environment...");
			logger.error(e.getLocalizedMessage());
			if (logger.isDebugEnabled()) {
				String stack = LogUtils.getStackTrace(e);
				logger.debug(stack);
			}
		}
	}

	/**
	 * Initialize the Global environment. Should be called before the system is
	 * started.
	 * 
	 * net.langene.tabvd.core.Global
	 * 
	 * @param configfile
	 *            - Configuration filename.
	 * @throws Exception
	 */
	public static void init(String configfile) throws Exception {
		synchronized (isInitialized) {
			if (isInitialized)
				return;
			// Load the specified configuration.
			_config = new XMLConfiguration(configfile);
			// Configure the system logging framework.
			configLogger();
			// Configure the Database connection.
			configDb();
			isInitialized = true;
			logger.info("Server initialization completed...");
		}
	}

	private static void configDb() throws Exception {
		String dbdir = _config
				.getString(Constants.Configuration._HSQLDB_DB_DIRECTORY_);
		if (dbdir == null || dbdir.isEmpty())
			throw new Exception("Invalid Database Configuration : Missing ["
					+ Constants.Configuration._HSQLDB_DB_DIRECTORY_ + "]");
		String dbname = _config
				.getString(Constants.Configuration._HSQLDB_DB_NAME_);
		if (dbname == null || dbname.isEmpty())
			throw new Exception("Invalid Database Configuration : Missing ["
					+ Constants.Configuration._HSQLDB_DB_NAME_ + "]");
		String dbuser = _config
				.getString(Constants.Configuration._HSQLDB_DB_USERNAME_);
		if (dbuser == null || dbuser.isEmpty())
			throw new Exception("Invalid Database Configuration : Missing ["
					+ Constants.Configuration._HSQLDB_DB_USERNAME_ + "]");
		String dbpassw = _config
				.getString(Constants.Configuration._HSQLDB_DB_PASSWORD_);
		if (dbpassw == null || dbpassw.isEmpty())
			throw new Exception("Invalid Database Configuration : Missing ["
					+ Constants.Configuration._HSQLDB_DB_PASSWORD_ + "]");
		String connurl = "jdbc:hsqldb:file:" + dbdir + "/" + dbname
				+ ";shutdown=true";
		_dbconn = DriverManager.getConnection(connurl, dbuser, dbpassw);
		_dbconn.setAutoCommit(true);

		logger.info("tabvd Database initialized using connection url ["
				+ connurl + "], DB User : " + dbuser);
		DbInstaller installer = new DbInstaller(_dbconn);
		_schema = installer.checkSetup();
	}

	/**
	 * Initialize the logging environment. In case of errors the system will
	 * restore to the default configuration. [STDOUT]
	 * 
	 * net.langene.tabvd.core.Global
	 * 
	 */
	private static void configLogger() {
		try {
			String logdir = _config
					.getString(Constants.Configuration._LOGBACK_DIRECTORY_);
			if (logdir == null || logdir.isEmpty())
				logdir = System.getProperty("java.io.tmpdir");
			System.setProperty("TABVD_LOG_DIR", logdir);

			String logconfig = _config
					.getString(Constants.Configuration._LOGBACK_CONFIGFILE_);
			if (logconfig != null && !logconfig.isEmpty()) {
				File fi = new File(logconfig);
				if (fi.exists() && fi.canRead()) {
					// assume SLF4J is bound to logback in the current
					// environment
					LoggerContext lc = (LoggerContext) LoggerFactory
							.getILoggerFactory();
					JoranConfigurator configurator = new JoranConfigurator();
					configurator.setContext(lc);
					// the context was probably already configured by default
					// configuration
					// rules
					lc.reset();
					configurator.doConfigure(logconfig);
					StatusPrinter.printInCaseOfErrorsOrWarnings(lc);
				}
			}
			printLogHeader();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static void printLogHeader() {
		logger.info("tabvd Server initializing...");
		logger.info("Using system configuration file " + _config.getFileName()
				+ "...");
	}
}
